/*----------------------------------------------------------------------
 * File Name:       avdl_timmod.c
 *----------------------------------------------------------------------
  This file is provided under a dual BSD/GPLv2 license.  When using or 
  redistributing this file, you may do so under either license.

  GPL LICENSE SUMMARY

  Copyright(c) 2007-2009 Intel Corporation. All rights reserved.

  This program is free software; you can redistribute it and/or modify 
  it under the terms of version 2 of the GNU General Public License as
  published by the Free Software Foundation.

  This program is distributed in the hope that it will be useful, but 
  WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
  General Public License for more details.

  You should have received a copy of the GNU General Public License 
  along with this program; if not, write to the Free Software 
  Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
  The full GNU General Public License is included in this distribution 
  in the file called LICENSE.GPL.

  Contact Information:
   Intel Corporation

   2200 Mission College Blvd.
   Santa Clara, CA  97052

  BSD LICENSE 

  Copyright(c) 2007-2009 Intel Corporation. All rights reserved.
  All rights reserved.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

    * Redistributions of source code must retain the above copyright 
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright 
      notice, this list of conditions and the following disclaimer in 
      the documentation and/or other materials provided with the 
      distribution.
    * Neither the name of Intel Corporation nor the names of its 
      contributors may be used to endorse or promote products derived 
      from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 */



#include "avdl_timmod_pvt_structs.h"
#ifndef _USER_TEST_
#include "osal_assert.h"
#endif

//TODO: take account of source buffer level 
//TODO: check all the bad cases can recover back (DMB-TH, ...)
//TODO: due to PTS gap between channels, average delta transition time should be different between channels.
//TODO: Using object address as handle instead of array index?
//TODO: how to handle an abnormal first point 
static timmod_t timmod[MAX_INSTANCE];

static void init_timmod_globals(timmod_t *timmod)
{
   //invalidate globals
   timmod->average_delta = 0;
   timmod->state = TIMMOD_STATE_START;
}

//init instance
static void init_timmod_instance(timmod_t *timmod)
{
   int i;

   timmod->base_time = 0;
   timmod->play_rate = 1;
   timmod->clock_handle = -1;
   timmod->source_handle = NULL; 

   for(i = 0; i< MAX_CHANNEL; i++) {
#ifndef _USER_TEST_
      timmod->channel[i].lock = os_create_lock();
#endif
      timmod->channel[i].used = 0;
      timmod->channel[i].channel_index = MAX_CHANNEL;
   }

   init_timmod_globals(timmod);
}

static void init_timmod_channel(channel_t *c, uint32_t channel_index)
{
   c->used = 1;
   c->state = TIMMOD_STATE_START;
   c->render_handle = -1;
   c->write = 0;
   c->num_of_continuous_points = 0;
   c->num_of_points = 0;
   c->first_point = 1;
   c->channel_index = channel_index;
   c->last_average_delta = 0;
}


/**      
  Allocates TimMod handle.  Will set up an TimMod channel.

  @param[in] render_handle : audio/video/subtile device handle. This handle is used to get render buffer level.  
  @param[out] timmod_channel_handle : the returned channel handle of TimMod

  @retval AVDL_SUCCESS : timmod channel allocated successfully  
  @retval AVDL_INVALID_PARAM : bad parameters       
  @retval AVDL_NO_RESOURCE : run out of resources 
  */      

//TODO: need to reset timmod status.
avdl_result_t avdl_timmod_channel_open(avdl_timmod_handle_t timmod_handle, int render_handle, avdl_timmod_channel_handle_t *timmod_channel_handle)
{
   uint32_t i;
   timmod_t *timmod_cur;
	OS_INFO("Open Channel\n");

   if(timmod_handle >= MAX_INSTANCE || !timmod[timmod_handle].used){
		OS_INFO("Invalid Handle\n");	
      return AVDL_INVALID_HANDLE;
	}


   if( timmod_channel_handle == NULL){ 
		OS_INFO("Invalid Param\n");	
      return AVDL_INVALID_PARAM;
	}

   timmod_cur = &timmod[timmod_handle];

   for(i = 0; i<MAX_CHANNEL; i++) {
      os_lock(timmod_cur->channel[i].lock);
      if(timmod_cur->channel[i].used == 0) {
         init_timmod_channel(&timmod_cur->channel[i], i);      
         os_unlock(timmod_cur->channel[i].lock);
         break;
      }
      os_unlock(timmod_cur->channel[i].lock);
   }

   if(MAX_CHANNEL == i)
      return AVDL_NO_RESOURCES;

   //On channel open (in case of program change), need to reset to timmod state
   //TODO: or, user should first close timmod instance on channel change
   init_timmod_globals(timmod_cur);
   *timmod_channel_handle = i;
   return AVDL_SUCCESS;
}

avdl_result_t avdl_timmod_channel_close(avdl_timmod_handle_t timmod_handle, avdl_timmod_channel_handle_t timmod_channel_handle)
{
   timmod_t *timmod_cur;

   if(timmod_handle >= MAX_INSTANCE || !timmod[timmod_handle].used){
		OS_INFO("Invalid Handle\n");	
      return AVDL_INVALID_HANDLE;
   }

   timmod_cur = &timmod[timmod_handle];

   if(timmod_channel_handle >= MAX_CHANNEL || !timmod_cur->channel[timmod_channel_handle].used) {
		OS_INFO("Invalid Handle\n");	
      return AVDL_INVALID_HANDLE;
   }

   os_lock(timmod_cur->channel[timmod_channel_handle].lock);
   timmod_cur->channel[timmod_channel_handle].used = 0;
   os_unlock(timmod_cur->channel[timmod_channel_handle].lock);

   return AVDL_SUCCESS;
}

avdl_result_t avdl_timmod_set_base_time(avdl_timmod_handle_t timmod_handle, ismd_time_t base_time)
{
   if(timmod_handle >= MAX_INSTANCE || !timmod[timmod_handle].used){
		OS_INFO("Invalid Handle\n");	
      return AVDL_INVALID_HANDLE;
	}

   timmod[timmod_handle].base_time = base_time;

   return AVDL_SUCCESS;
}



//Allocate one TimMod instance. 
avdl_result_t avdl_timmod_open(avdl_timmod_handle_t *handle)
{
   int i;
   timmod_t *timmod_cur;

   if(!handle){
		OS_INFO("Invalid Param\n");	
      return AVDL_INVALID_PARAM;
	}

   for(i = 0; i<MAX_INSTANCE; i++) {
      os_lock(timmod[i].lock); 
      if(!timmod[i].used) {
         timmod[i].used = 1;
         *handle = i;
         os_unlock(timmod[i].lock); 
         break;
      }
      os_unlock(timmod[i].lock); 
   }
   
   if(MAX_INSTANCE == i)
      return AVDL_NO_RESOURCES;

   timmod_cur = &timmod[i];
   init_timmod_instance(timmod_cur);

   return AVDL_SUCCESS;
}

avdl_result_t avdl_timmod_close(avdl_timmod_handle_t handle)
{
   avdl_result_t result;
   timmod_t *timmod_cur;
   int i;

   if(handle >= MAX_INSTANCE || !timmod[handle].used){
		OS_INFO("Invalid Handle\n");	
      return AVDL_INVALID_HANDLE;
	}

   timmod_cur = &timmod[handle];

   os_lock(timmod_cur->lock);
   for(i = 0; i< MAX_CHANNEL; i++) {
      if(timmod_cur->channel[i].used) {
         result = avdl_timmod_channel_close(handle, i);
         assert(AVDL_SUCCESS == result);
      }
#ifndef _USER_TEST_
      os_destroy_lock(timmod_cur->channel[i].lock);
#endif
   }
   timmod_cur->used = 0;
   os_unlock(timmod_cur->lock);

   printk("TIMMOD CLOSED\n");
   return AVDL_SUCCESS;
}




//TimMod module init, can only be called on module initialization
void timmod_init()
{
   int i;

   for(i = 0; i<MAX_INSTANCE; i++) {
      timmod[i].used = 0;
#ifndef _USER_TEST_
      timmod[i].lock = os_create_lock();
#endif
   }
}

void timmod_deinit()
{
   int i;

   for(i = 0; i<MAX_INSTANCE; i++) {
      if(timmod[i].used)
         avdl_timmod_close((avdl_timmod_handle_t)i);
#ifndef _USER_TEST_
      os_destroy_lock(timmod[i].lock);
#endif
   }
}

avdl_result_t avdl_timmod_set_clock(avdl_timmod_handle_t timmod_handle, ismd_clock_handle_t handle)
{
   if(timmod_handle >= MAX_INSTANCE || !timmod[timmod_handle].used){
		OS_INFO("Invalid Handle\n");	
      return AVDL_INVALID_HANDLE;
	}

   timmod[timmod_handle].clock_handle = handle;

   return AVDL_SUCCESS;
}

avdl_result_t avdl_timmod_set_source(avdl_timmod_handle_t timmod_handle, avdl_tsi_handle_t *handle)
{
   if(timmod_handle >= MAX_INSTANCE || !timmod[timmod_handle].used){
		OS_INFO("Invalid Handle\n");	
      return AVDL_INVALID_HANDLE;
	}

   timmod[timmod_handle].source_handle = handle;

   return AVDL_SUCCESS;
}

//TODO: handle negative play_rate?
avdl_result_t avdl_timmod_set_play_rate(avdl_timmod_handle_t timmod_handle, int play_rate)
{
   if(timmod_handle >= MAX_INSTANCE || !timmod[timmod_handle].used){
		OS_INFO("Invalid Handle\n");	
      return AVDL_INVALID_HANDLE;
	}

   timmod[timmod_handle].play_rate = play_rate;

   return AVDL_SUCCESS;
}


//Functionality: calculate the transformed PTS
//if the TimMod state is STABLE/START, PTS' = PTS - delta
//if the TimMod state is DISCONTINUITY, PTS' = 0(invalid)
//TODO!!: double check the pts_out is near theory line. (get clock, and calc stream time locally)
static ismd_time_t calc_pts_out(timmod_t *timmod, ismd_time_t pts_in)
{
   if(timmod->state == TIMMOD_STATE_START || timmod->state == TIMMOD_STATE_STABLE) {
	   printk("timmod average delta:%lld\n",timmod->average_delta);
      //TODO: check every path has a average delta
      return pts_in-timmod->average_delta;
   } else if (timmod->state == TIMMOD_STATE_DISCONTINUITY) {
      return 0;
   } else {
      assert(0);
   }
}

//Judge whether all in-used channel in the same state
static inline int all_channels_in_state(timmod_t *timmod, timmod_state_t state)
{
   int i;

   for(i = 0; i<MAX_CHANNEL; i++) {
      if(timmod->channel[i].used && timmod->channel[i].state != state)
         return 0;
   }

   return 1;
}

//Functionality: calculated one channel's average delta
//If continuous == 1, only take count of countinuous points
//else take count of all points
//Return : 0 if can not get a valid output (channel not used or no available point)
//         an average delta in other case
static int channel_average_delta(channel_t *c, int continuous)
{
   int points = 0;
   int total_delta = 0;
   int average_delta = 0;
//   this point is trustable when arrive here
//   uint32_t idx = c->write==0?NUMBER_OF_HISTORY_DATA-1:c->write-1; 
   uint32_t idx = c->write; 
   int i;

   if(!c->used)
      return 0;

   printk("num of p:%d\tpts_t_by_usingtime:%lld\n",c->num_of_points,c->p[idx].pts_theory_by_using_time);

   if(continuous)
      points = c->num_of_continuous_points>POINTS_OF_AVERAGE_DELTA?POINTS_OF_AVERAGE_DELTA:c->num_of_continuous_points;
   else
      points = c->num_of_points>POINTS_OF_AVERAGE_DELTA?POINTS_OF_AVERAGE_DELTA:c->num_of_points;

   if(!points)
      return 0;

   for(i = 0; i<points; i++) {
      total_delta += c->p[idx].pts_in - c->p[idx].pts_theory_by_using_time;
      idx = idx==0?NUMBER_OF_HISTORY_DATA-1:idx-1;
   }

   average_delta = total_delta/points;
   printk("total_delta:%d\taverage_delta:%d\n",total_delta,average_delta);
   return average_delta;
}

static inline int abs_sub(int a, int b)
{
   return a>b?a-b:b-a;
}

static void update_last_change_time (timmod_t *timmod)
{
   ismd_time_t current_time;

#ifdef _USER_TEST_
   get_pc_time(&current_time);
#else
   ismd_result_t result;
   result = ismd_clock_get_time(timmod->clock_handle, &current_time);
   assert(ISMD_SUCCESS == result);
#endif
   timmod->average_delta_last_change_time = current_time;
}


//Functionality: update the timmod's average delta
//This function will not hold the timmod.lock, caller should first hold this lock before calling
//If timmod in state start: average deltat is calculated by the average of last n points(n < NUMBER_OF_HISTORY_DATA)
//If timmod in state stable : average deltat is the average of last n continuity points (continuous points without discontinuity) 
//                            only when the current_time channel average delta floated a lot from last one, the timmod.average_delta is updated
//If timmod in state discontinuity: this is called on the edge of discontinuity->stable transition. 
//                                  average deltat is the average of last n continuity points (continuous points without discontinuity)
//TODO: should take count of the last average delta in certain weight (only in case of stable state update) to make a gradule change?
static void update_average_delta(timmod_t *timmod)
{
   int sum_of_channel_delta = 0;
   int channel_delta = 0;
   int channels = 0;
   int i;
   int floated = 0;

   if(timmod->state == TIMMOD_STATE_START) {
      for (i = 0; i< MAX_CHANNEL; i++) {
         //try total points instead of only continuous ones in start stage
         channel_delta = channel_average_delta(&timmod->channel[i], 0);
         if(channel_delta) {
            timmod->channel[i].last_average_delta = channel_delta;
            sum_of_channel_delta += channel_delta;
            channels += 1;
         }
      }
   } else if (timmod->state == TIMMOD_STATE_STABLE) {
      //Only change average_delta when we floated a lot  
      for (i = 0; i< MAX_CHANNEL; i++) {
         channel_delta = channel_average_delta(&timmod->channel[i], 1);
         if(channel_delta) {
            if (abs_sub(channel_delta, timmod->channel[i].last_average_delta) > AVERAGE_DELTA_FLOATING_THRESHOLD) {
               //TODO: last_average_delta should be updated on not floated channel if one channel floated
               //this lead no severe consequence except more frequent average_delta change
               timmod->channel[i].last_average_delta = channel_delta;
               floated += 1;
            }
            sum_of_channel_delta += channel_delta;
            channels += 1;
         }
      }
      if (!floated)
         channels = 0;
   } else {
      for (i = 0; i< MAX_CHANNEL; i++) {
         channel_delta = channel_average_delta(&timmod->channel[i], 1);
         if(channel_delta) {
            timmod->channel[i].last_average_delta = channel_delta;
            sum_of_channel_delta += channel_delta;
            channels += 1;
         }
      }
   }

   if(!channels)
      return;

   timmod->average_delta = sum_of_channel_delta/channels;
   update_last_change_time (timmod);
}

//Update TimMod's state
//Transit to discontinuity directly
//Transit to stable when all channel in use is in stable state
static void update_timmod_state(timmod_t *timmod, timmod_state_t target_state)
{
   os_lock(timmod->lock);
   if (target_state == TIMMOD_STATE_DISCONTINUITY) {
      printk("timmod state transit from %d to %d DISCON\n", timmod->state, TIMMOD_STATE_DISCONTINUITY);
      timmod->state = TIMMOD_STATE_DISCONTINUITY;
   }
   else if (TIMMOD_STATE_STABLE == target_state) {
      assert(timmod->state != TIMMOD_STATE_STABLE);
      if(all_channels_in_state(timmod, TIMMOD_STATE_STABLE)) {
         printk("1:timmod state transit from %d to %d STABLE\n", timmod->state, TIMMOD_STATE_STABLE);
         update_average_delta(timmod);
         timmod->state = TIMMOD_STATE_STABLE;
      }
   } else {
      printk("START can not be the target of a state transition\n");
      assert(0);
   }

   os_unlock(timmod->lock);
}

//Clock difference taking care of wrap around
static inline ismd_time_t time_elapsed(ismd_time_t previous, ismd_time_t current_time)
{
   return current_time>previous?current_time-previous:0xFFFFFFFFFFFFFFFFULL-previous+current_time;
}

//Functionality: update both the state of specific channel and TimMod
//TODO: handle the START state very carefully
//      Main difference between START and DISCONTINUITY state: 
//      We do not have a feasible average delta in start state. So when the first channel's 
//      first point arrive, some guess must be made and may suffer to modification when other channel's
//      first pints arrive.
//      1. One idea is to trust the first channnel's first point and transit 
//      TimMod's state into STABLE. The risk of this method is that the avergage
//      delta by the first point may not meet other channel's requirement (the translated
//      point may be below the theory line)
//      Also, the futher points may not be continuous, (This is OK, because this will cause the system 
//      shift to discontinuity state.)
//      2. Another idea is to transit to STABLE after continuous n points pass the LP.
//TODO!!: update average_delta and make sure we have this on each code path.
//TODO!!: make sure the change frequency of average_delta is limited
static void update_channel_state(timmod_t *timmod, channel_t *c, int current_time_point_passed_filter)
{
   ismd_time_t current_time;
#ifndef _USER_TEST_
   ismd_result_t result;
#endif
   ismd_time_t elapsed;

   if (current_time_point_passed_filter) {
      c->num_of_continuous_points += 1;
      c->num_of_points += 1;
      switch (c->state) {
         case TIMMOD_STATE_START:
         case TIMMOD_STATE_DISCONTINUITY:
            if (c->num_of_continuous_points >= TIMMOD_STABLIZE_POINTS) {
               printk("2:Channel %d state transit from %d to %d STABLE\n",\
					   c->channel_index, c->state, TIMMOD_STATE_STABLE);
               c->state = TIMMOD_STATE_STABLE;
               update_timmod_state(timmod, TIMMOD_STATE_STABLE);
            }
            break;
         case TIMMOD_STATE_STABLE:
            //A point to change average_delta to avoid accumulated errors even in stable state.....
            //average_delta should not change frequently
            if(c->write != 0)
               return;

#ifdef _USER_TEST_
			get_pc_time(&current_time);
#else
            result = ismd_clock_get_time(timmod->clock_handle, &current_time); 
            assert(ISMD_SUCCESS == result);
#endif
            elapsed = time_elapsed(timmod->average_delta_last_change_time, current_time);
            if(elapsed < AVERAGE_DELTA_CHANGE_FREQUENCY)
               return;

            update_average_delta(timmod);
            break;
         default:
            assert(0);
            break;
      }
   } else {
      //NOTE: num_of_points will not reset
      //Discard old points, shift to new ones here.
	   c->num_of_continuous_points = 0;
      switch (c->state) {
         case TIMMOD_STATE_START:
		   c->first_point = 1;
		   c->num_of_points = 0;
         break;
         case TIMMOD_STATE_DISCONTINUITY:
         break;

         case TIMMOD_STATE_STABLE:
         printk("Channel %d state transit from %d to %d DISCON\n",\
				 c->channel_index, c->state, TIMMOD_STATE_DISCONTINUITY);
         c->state = TIMMOD_STATE_DISCONTINUITY;
         update_timmod_state(timmod, TIMMOD_STATE_DISCONTINUITY);
         break;
         default:
         assert(0);
         break;
      }
	   printk("points not passed filter,continous points:%d\n",c->num_of_continuous_points);
   }
}

//return the previous point
static inline uint32_t previous_point(uint32_t write)
{
   uint32_t idx;
   
   assert (write < NUMBER_OF_HISTORY_DATA);
   if(write > 0)
      idx = write - 1;
   else
      idx = NUMBER_OF_HISTORY_DATA -1;

   return idx;
}

//Check whether pts output is located in an upper and lower bandwidth of pts_theory 
//Return: 1 if pass the filter
//        0 if not pass the filter
static inline int pts_out_bp_filter(ismd_time_t pts_out, ismd_time_t pts_theory)
{
	int64_t threshold = (int64_t)pts_theory - (int64_t)TIMMOD_LP_THRESHOLD;
   if((int64_t)pts_out > (int64_t)(pts_theory + TIMMOD_LP_THRESHOLD) || (int64_t)pts_out < (int64_t)(pts_theory - TIMMOD_LP_THRESHOLD)){ 
		printk("pts out:%d\tpts theory:%d\n",pts_out,pts_theory);
      return 0;
	}
   else{ 
      return 1;
	}
}

//A simple low pass filter for secondary delta 
//Return: 1 if pass the filter
//        0 if not pass the filter
static inline int secondary_delta_lp_filter(int delta)
{
   if(delta < 0)
      delta = 0 - delta;

   if (delta > TIMMOD_SECONDARY_LP_THRESHOLD)
      return 0;
   else 
      return 1;
}

//First point is first point
//We always trust the first channel's first point and using it to calc the first average delta
//1. If the first channel's first point is proved to be a odd point, the simple way is that, 
//the system will run by interpolation until it transit to stable state and do a shift hereafter. 
//2. TODO: If the first average delta can not meet the requirement of other channel.At least,
//other channel need a start point for interpolation. The idea is we need to turn to continuous
//points in start state and calculate a average delta. (Yes, we may can not get a continuous average!)
static void handle_first_point(timmod_t *timmod, channel_t *c, ismd_time_t pts_in, ismd_time_t pts_theory, int64_t pts_delta)
{
   assert(c->state == TIMMOD_STATE_START);
   //assert(c->write == 0);
   assert(c->num_of_continuous_points == 0);

   //TODO: may not need this variale?
   c->num_of_points += 1;
   //trust the first point, record it, calc/update the average_delta if necessary
   //
   os_lock(timmod->lock);

   //This is the first channel's first point
   if(timmod->average_delta == 0) {
      //the first guessed average delta is calculated by 
      //putting the first point to exactly the theory line
      update_average_delta(timmod);

      c->p[c->write].pts_out = pts_theory;
   } else {  //Otherwise, check whether we need to modify the average delta
      c->p[c->write].pts_out = pts_in - timmod->average_delta;
		printk("delta:%d\n",timmod->average_delta);
      //the previously guessed average_delta can not meet the second channel's required filter.
      if(!pts_out_bp_filter(c->p[c->write].pts_out, pts_theory)) {
         //new guess is done by the average of this and previous guess
         //TODO: if oscillation here, please enlarge TIMMOD_LP_THRESHOLD;
         printk("Average_delta change during system startup from %lld", timmod->average_delta);

         update_average_delta(timmod);

         printk("to %lld\n", timmod->average_delta);

         //check whether the new guessed meet the requirment.
         c->p[c->write].pts_out = pts_in - timmod->average_delta;
         if(!pts_out_bp_filter(c->p[c->write].pts_out, pts_theory)) {
            printk("please adjust TIMMOD_LP_THRESHOLD:%d\n",(uint32_t)TIMMOD_LP_THRESHOLD);
            assert(0);
         }
      }
   }

   os_unlock(timmod->lock);

   c->first_point = 0;
   c->write++;
}

//Functionality: Add or filter out one input point. Update state machine accordingly.
//Channel state is changed between STABLE and DISCONTINUITY after startup, TimMod's state is STABLE when ALL
//channel is STABLE; and TimMod's state is DISCONTINUITY when one channel is DISCONTINUITY.
//Return: 1 if point added to history
//        0 if point filtered out by filter.
//Assumption: only one thread call pts_transform simultaneously for one channel, so no lock needed here
//TODO: try arrival time.
//TODO: check, arrival time or using time maybe zero (invalid)
static int add_point(timmod_t *timmod, channel_t *c, ismd_time_t pts_in, ismd_time_t arrival_time, ismd_time_t using_time)
{
   ismd_time_t ptsta;
   ismd_time_t ptstu;
   int64_t deltaa;
   int64_t deltau;
   //int64_t sdeltaa;
   int64_t sdeltau;

   ptsta = (arrival_time )*timmod->play_rate + TIMMOD_THEORY_BUFFERING; 
   ptstu = (using_time )*timmod->play_rate + TIMMOD_THEORY_BUFFERING; 
   deltaa = pts_in - ptsta;
   deltau = pts_in - ptstu;

   //TODO: may not need to record so much
   c->p[c->write].pts_in = pts_in;
   c->p[c->write].arrival_time = arrival_time;
   c->p[c->write].using_time = using_time;
   c->p[c->write].pts_theory_by_arrival_time = ptsta;
   c->p[c->write].pts_theory_by_using_time = ptstu;
   c->p[c->write].delta_arrival = deltaa;
   c->p[c->write].delta_using = deltau;

   if(c->first_point) {
      handle_first_point(timmod, c, pts_in, ptstu, deltau);
	  c->prev_deltau = deltau;

      //No state machine change needed for the first point
      return 1;
   }

   //Not the first point at least.
   //first do the secondary delta low pass filtering.
//   sdeltau = deltau - c->p[previous_point(c->write)].delta_using;
   sdeltau = deltau - c->prev_deltau;
   c->prev_deltau = deltau;
   if (secondary_delta_lp_filter(sdeltau)) {
      c->p[c->write].secondary_delta_using = deltau;
      update_channel_state(timmod, c, 1);
      c->write++;
      return 1;
   } else {
      update_channel_state(timmod, c, 0);
      return 0;
   }
}

avdl_result_t avdl_timmod_pts_transform(avdl_timmod_handle_t timmod_handle, 
                                        avdl_timmod_channel_handle_t timmod_channel_handle, 
                                        ismd_time_t pts_in, ismd_time_t arrival_time, 
                                        ismd_time_t using_time, ismd_time_t *pts_out)
{
   channel_t *c;
   timmod_t *timmod_cur;

   if(timmod_handle >= MAX_INSTANCE || !timmod[timmod_handle].used){
		OS_INFO("Invalid Handle:%d\n",timmod_handle);	
      return AVDL_INVALID_HANDLE;
	}

   timmod_cur = &timmod[timmod_handle];

   if(timmod_channel_handle >= MAX_CHANNEL || !timmod_cur->channel[timmod_channel_handle].used || !pts_out){ 
		OS_INFO("Invalid Param\n");	
      return AVDL_INVALID_PARAM;
	}
	printk("pts in:%lld\t",pts_in);
	printk("usingtime:%lld\n",using_time);

   assert(pts_in);
   assert(arrival_time);
   assert(using_time);

   c = &timmod_cur->channel[timmod_channel_handle];

   //if this point filtered out by filters, discard this point
   if (!add_point(timmod_cur, c, pts_in, arrival_time, using_time)) {
		printk("Discard pts\n");
      *pts_out = 0;
      return AVDL_SUCCESS;
   }

   *pts_out = calc_pts_out(timmod_cur, pts_in);
   return AVDL_SUCCESS;
}

